#!/usr/bin/python
#
# Copyright (c) 2008, Mathias Weber
# Copyright (c) 2008, Thomas Stauffer
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# $Id$

import sys
import random
import math

from pandac.PandaModules import AmbientLight
from pandac.PandaModules import Point3
from pandac.PandaModules import PointLight
from pandac.PandaModules import VBase4

from direct.interval.LerpInterval import LerpFunc

import direct.directbase.DirectStart

base.setBackgroundColor(0.0, 0.0, 0.2)
base.disableMouse()

"""
There is no possibility to calculate a radius for a point light or to set a
radius for a point light. Given the standard equation:

attenuation = 1.0 / (a + b * d + c * d ^ 2)

where d is the distance from the light to the lit vertex/pixel, every point
light has an infinite radius. You may ask, why do we need a radius? With a given
radius we can calculate which nodes the light may lit. Because this scene has
more than 8 (limit of the hardware) lights, we only enable light on a node if it
a possible that light illuminate that node.

In this example we simplify the attenuation equation to:

attenuation = 1.0 / (c * d ^ 2)

Given this eqation we can easily calculate a radius if we define a minimum
attenuation where we define that light is no more visible. The smaller this
minimum attenuation, the larger the light radius.

If you write your own lighting shader, than chose a better attenuation function.
It is only here, to compare the visual output with standard attuenation function
in vertex lighting.

Note: If you look close at the scene you can see yellow rings. First I thought
there is an bug in the shader (not that there is none). But after I took a
screenshoot, I was able to see that the RGB triplets were gradually increasing.
In this example (no textures) 24 bit per color are not enough for our eyes.
"""

ATTENUATION_COEFFICIENT = 10.0
ATTENUATION_MIN = 0.02
INFLUENCE_RADIUS = math.sqrt(1.0 / (ATTENUATION_MIN * ATTENUATION_COEFFICIENT))

PER_PIXEL_LIGHTING = True

SIZE = 15
LIGHTS = 30

camera.setHpr(0.0, -90.0, 0.0)
camera.setPos((SIZE - 1) * 0.5, (SIZE - 1) * 0.5, 30.0)

shader = loader.loadShader("example-lights.sha")

world = render.attachNewNode("World")

blockNodes = []
for y in range(SIZE):
    for x in range(SIZE):
        blockNode = loader.loadModel("block")
        blockNode.reparentTo(world)
        blockNode.setPos(x, y, 0.0)
        blockNode.setPythonTag("LightCount", 0)
        blockNodes += [ blockNode ]

#world.flattenLight()

if PER_PIXEL_LIGHTING:
    """
    We add a default light to each block, that is so far away, that it cannot
    brighten any block.
    """

    lightAwayNode = world.attachNewNode("Light")
    lightAwayNode.setPos(1000.0, 1000.0, 1000.0)

    world.setShader(shader)
    world.setShaderInput("light0", lightAwayNode)
    world.setShaderInput("light1", lightAwayNode)
    world.setShaderInput("light2", lightAwayNode)
    world.setShaderInput("light3", lightAwayNode)
    world.setShaderInput("light4", lightAwayNode)
    world.setShaderInput("light5", lightAwayNode)
    world.setShaderInput("light6", lightAwayNode)
    world.setShaderInput("light7", lightAwayNode)

    lights = []
    for i in range(LIGHTS):
        x = random.random() * SIZE - 0.5
        y = random.random() * SIZE - 0.5
        z = random.random() * 0.5 + 0.5

        lightNode = render.attachNewNode("Light")
        lightNode.setPos(x, y, z)

        # Pointlight is a model from the Panda3D engine.
        lightDummyNode = loader.loadModel("misc/Pointlight")
        # Try to avoid setScale in when using shaders (normalization issues).
        lightDummyNode.setScale(0.1)
        lightDummyNode.reparentTo(lightNode)

        for blockNode in blockNodes:
            center = blockNode.getBounds().getCenter()
            radius = blockNode.getBounds().getRadius()
            distance = (lightNode.getPos() - center).length() - radius

            if distance < INFLUENCE_RADIUS:
                n = blockNode.getPythonTag("LightCount")
                if n <= 8:
                    pass
                    blockNode.setShaderInput("light" + str(n), lightNode)
                blockNode.setPythonTag("LightCount", n + 1)

if not PER_PIXEL_LIGHTING:
    """
    We need this default light here, so blocks without any light, are not white,
    because lighting is disabled by Panda3D.
    """
    light = AmbientLight("Light")
    light.setColor(VBase4(0.0, 0.0, 0.0, 1.0))
    lightNode = render.attachNewNode(light)
    world.setLight(lightNode)

    lights = []
    for i in range(LIGHTS):
        x = random.random() * SIZE - 0.5
        y = random.random() * SIZE - 0.5
        z = random.random() + 0.5

        light = PointLight("Light")
        light.setColor(VBase4(1.0, 1.0, 0.0, 1.0))
        light.setAttenuation(Point3(0.0, 0.0, ATTENUATION_COEFFICIENT))

        lights += [ light ]

        lightNode = render.attachNewNode(light)
        lightNode.setPos(x, y, z)

        lightDummyNode = loader.loadModel("misc/Pointlight")
        lightDummyNode.setScale(0.1)
        lightDummyNode.reparentTo(lightNode)

        for blockNode in blockNodes:
            center = blockNode.getBounds().getCenter()
            radius = blockNode.getBounds().getRadius()
            distance = (lightNode.getPos() - center).length() - radius

            if distance < INFLUENCE_RADIUS:
                blockNode.setLight(lightNode)
                blockNode.setPythonTag("LightCount", blockNode.getPythonTag("LightCount") + 1)

n = 0
for blockNode in blockNodes:
    n = max(n, blockNode.getPythonTag("LightCount"))

print "-" * 80
print "blocks", SIZE * SIZE
print "influence radius", INFLUENCE_RADIUS
print "max light count on one node", n, "(> 8 = bad)"
print "-" * 80

base.accept("escape", sys.exit)
base.accept("a", render.analyze)
base.accept("o", base.oobe)

base.accept("1", blockNodes[0].showBounds)
base.accept("2", blockNodes[0].hideBounds)

run()
